// Copyright (c) 2006 David Vescovi.  All rights reserved.
// Part of Project DrumStix
// Windows Embedded Developers Interest Group (WE-DIG) community project.
// http://www.we-dig.org

#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Gumstix;
using OpenNETCF.IO;
#endregion

namespace Gumstix.IO
{
    /// <summary>
    /// managed wrapper for accessing the AudioStix features
    /// and functions.
    /// </summary>
    unsafe public class AudioStix : StreamInterfaceDriver
    {
        [Flags]
        public enum GPIO : ushort
        {
            GPIO_0 = 0x0001,
            GPIO_1 = 0x0002,
            GPIO_2 = 0x0004,
            GPIO_3 = 0x0008,
            GPIO_4 = 0x0010,
            GPIO_5 = 0x0020,
            GPIO_6 = 0x0040,
            GPIO_7 = 0x0080,
            GPIO_8 = 0x0100,
            GPIO_9 = 0x0200
        }

        public enum InOut
        {
            Input,
            Output
        }




        #region Wave device IOCTL codes
        private const Int32 CODE_IOCTL_WAV_MESSAGE = 3;

        // read/write AC97 messages
        private const Int32 WPDM_PRIVATE_WRITE_AC97 = 0x0000040A;
        private const Int32 WPDM_PRIVATE_READ_AC97  = 0x0000040B;

//        private const Int32 FILE_DEVICE_SOUND = 0x00000022;
        private const Int32 FILE_DEVICE_SOUND = 0x0000001d;
        private const Int32 FILE_ANY_ACCESS = 0x0;
        private const Int32 METHOD_BUFFERED = 0x0;

        private static byte[] inbuffer = new byte[20];
        private static byte[] outbuffer = new byte[4];

        private const Int32 IOCTL_WAV_MESSAGE =
            ((FILE_DEVICE_SOUND) << 16) | ((FILE_ANY_ACCESS) << 14)
            | ((CODE_IOCTL_WAV_MESSAGE) << 2) | (METHOD_BUFFERED);

        /// <summary>
        /// UCB1400 register offsets
        /// </summary>
        private const Int32 IODATAREGISTER      = 0x5A;
        private const Int32 IODIRECTIONREGISTER = 0x5C;

        #endregion

        #region Native interface structures

        [StructLayout(LayoutKind.Sequential)]
        private struct WaveParameters
        {
            public Int32 DeviceId;
            public Int32 Message;
            public Int32 User;
            public Int32 Parameter1;
            public Int32 Parameter2;
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct WaveParameters2
        {
            public Int32 DeviceId;
            public Int32 Message;
            public Int32 User;
            public Int32 Parameter1;
            public UInt16* Parameter2;
        }
        #endregion

        #region ctor / dtor
        /// <summary>
        /// Provides access to I/O resident on the AudioStix board.
        /// </summary>
        public AudioStix() : base("WAV1:")
        {
            // open the driver
            Open(FileAccess.ReadWrite, FileShare.ReadWrite);
        }

        ~AudioStix()
        {
            // close the driver
            Close();
        }
        #endregion

        #region UCB1400 register access
        private UInt16 GetUCB1400Register(Int32 registerOffset)
        {
            WaveParameters2 wp = new WaveParameters2();
            UInt16 data;
            byte[] outbuffer = new byte[4];
            wp.Message = WPDM_PRIVATE_READ_AC97;
            wp.Parameter1 = registerOffset;
            wp.Parameter2 = &data;
            this.DeviceIoControl(IOCTL_WAV_MESSAGE, SerializeToByteArray(wp), outbuffer);
            if (BitConverter.ToUInt32(outbuffer, 0) != 0)
            {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "IOControl call failed");
            }
            return (data);
        }

        
        private void SetUCB1400Register(Int32 registerOffset, UInt16 Data)
        {
            WaveParameters wp = new WaveParameters();

            byte[] outbuffer = new byte[4];
            wp.Message = WPDM_PRIVATE_WRITE_AC97;
            wp.Parameter1 = registerOffset;
            wp.Parameter2 = Convert.ToInt32(Data);

            this.DeviceIoControl(IOCTL_WAV_MESSAGE, SerializeToByteArray(wp), outbuffer);
            if (BitConverter.ToUInt32(outbuffer, 0) != 0)
            {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "IOControl call failed");
            }
        }
        #endregion

        #region GPIO bit manipulation
        /// <summary>
        /// Sets up the GPIO direction.
        /// </summary>
        /// <param name="gpio">
        /// The GPIO(s) enum flags
        /// </param>
        /// <param name="inout">
        /// Input/Output
        /// </param>
        public void SetGPIODirection(GPIO gpio, InOut inout)
        {
            lock (typeof(AudioStix))
            {
                switch (inout)
                {
                    case InOut.Input:
                        SetUCB1400Register(IODIRECTIONREGISTER, (UInt16)(GetUCB1400Register(IODIRECTIONREGISTER) & (UInt16)(~(UInt16)gpio)));
                        break;
                    case InOut.Output:
                        SetUCB1400Register(IODIRECTIONREGISTER, (UInt16)(GetUCB1400Register(IODIRECTIONREGISTER) | (UInt16)gpio));
                        break;
                }
            }
        }

        public UInt16 GetGPIODirection()
        {
            lock (typeof(AudioStix))
            {
                return GetUCB1400Register(IODIRECTIONREGISTER);
            }
        }

        /// <summary>
        /// Sets a GPIO output bit or bits
        /// </summary>
        /// <param name="gpio">
        /// The GPIO(s) enum flags
        /// </param>
        public void BitSet(GPIO gpio)
        {
            lock (typeof(AudioStix))
            {
                SetUCB1400Register(IODATAREGISTER, (UInt16)(GetUCB1400Register(IODATAREGISTER) | (UInt16)gpio));
            }
        }

        /// <summary>
        /// Clears a GPIO output bit or bits
        /// </summary>
        /// <param name="gpio">
        /// The GPIO(s) enum flags
        /// </param>
        public void BitClear(GPIO gpio)
        {
            lock (typeof(AudioStix))
            {
                SetUCB1400Register(IODATAREGISTER, (UInt16)(GetUCB1400Register(IODATAREGISTER) & (UInt16)(~(UInt16)gpio)));
            }
        }

        /// <summary>
        /// Test a GPIO input bit
        /// </summary>
        /// <param name="gpio">
        /// GPIO(s) to test
        /// </param>
        /// <returns>
        /// Returns true if any of the selected GPIOs is high, false if 0
        /// </returns>
        public bool BitTest(GPIO gpio)
        {
           UInt16 bits;
           lock (typeof(AudioStix))
            {
               bits = (UInt16)(GetUCB1400Register(IODATAREGISTER) & (UInt16)gpio);
            }
            return (bits != 0);
        }


        #endregion

        #region P/Invokes and helpers

        /// <summary>
        /// Byte array serializer
        /// </summary>
        /// <param name="anything"></param>
        /// <returns></returns>
        private static byte[] SerializeToByteArray(object anything)
        {
            int rawsize = Marshal.SizeOf(anything);
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.StructureToPtr(anything, buffer, false);
            byte[] rawdatas = new byte[rawsize];
            Marshal.Copy(buffer, rawdatas, 0, rawsize);
            Marshal.FreeHGlobal(buffer);
            return rawdatas;
        }

        /// <summary>
        /// De-serializer from byte array
        /// </summary>
        /// <param name="rawdatas"></param>
        /// <param name="anytype"></param>
        /// <returns></returns>
        private static object DeserializeFromByteArray(byte[] rawdatas, Type anytype)
        {
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > rawdatas.Length)
                return null;
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(rawdatas, 0, buffer, rawsize);
            object retobj = Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }


        //[DllImport("coredll.dll", EntryPoint = "DeviceIoControl", SetLastError = true)]
        //internal static extern int DeviceIoControl(
        //    IntPtr hDevice,
        //    uint dwIoControlCode,
        //    byte[] lpInBuffer,
        //    int nInBufferSize,
        //    byte[] lpOutBuffer,
        //    int nOutBufferSize,
        //    ref int lpBytesReturned,
        //    IntPtr lpOverlapped);

        #endregion

    }
}
